﻿Imports Runa.Resources.ResourceManager
Imports Runa.Foundation.IO
Imports Runa.Common.Helper
Imports System.IO
Imports System

#If NETFX_CORE Then
Imports Windows.Foundation
Imports Windows.Foundation.Metadata
Imports System.Reflection
#End If

#If IRONPYTHON_AVAILABLE Then
Imports IronPython.Hosting
#End If

#If IRONRUBY_AVAILABLE Then
Imports Microsoft.Scripting
Imports IronRuby.StandardLibrary.BigDecimal
Imports Microsoft.Scripting.Hosting
Imports IronRuby
Imports IronRuby.Runtime
Imports IronRuby.Builtins
Imports IronRuby.StandardLibrary
#End If

Imports System.IO.IsolatedStorage
Imports System.Runtime.CompilerServices
Imports Microsoft.Xna.Framework
Imports Microsoft.Xna.Framework.Graphics
Imports Microsoft.Xna.Framework.Input

Namespace ScriptingHost

    Public Class ScriptTask

        Public Property Name() As String
        Public Property CommitTime() As DateTime
        Public Property ScriptType() As String
        Public Property Result() As Object
        Public Property LastError() As Exception

        Public Property Parameters() As New Dictionary(Of String, Object)

        Default Public Property Parameter(ByVal key As String) As Object
            Get
                If Me.Parameters.ContainsKey(key) Then
                    Return Me.Parameters(key)
                Else
                    Me.Parameters.Add(key, Nothing)
                    Return Nothing
                End If
            End Get
            Set(value As Object)
                If Me.Parameters.ContainsKey(key) Then
                    Me.Parameters(key) = value
                Else
                    Me.Parameters.Add(key, value)
                End If
            End Set
        End Property

        Public Property Script() As String
            Get
                Return Me.m_script
            End Get
            Set(value As String)
                Me.m_script = value
                If Me.m_script = "" Then
                    Return
                Else
                    If Me.ScriptScope Is Nothing Then
                        Me.ScriptScope = ScriptTask.Engine.CreateScriptSourceFromString(Me.Script)
                        Me.CompiledCode = Me.ScriptScope.Compile(New CompilationErrorListener(Me, AddressOf OnCompilationFailed))
                    End If
                End If
            End Set
        End Property

        Private Shared Property Engine() As ScriptEngine
        Public Property CompiledCode() As CompiledCode

        Private Property ScriptScope() As ScriptSource

        Private m_script As String = ""
        Private m_compiledCodes As New Dictionary(Of String, Tuple(Of ScriptSource, CompiledCode))

        Public Event OnRuntimeError(ByVal sender As Object, ByVal e As RuntimeErrorEventArgs)
        Public Event OnCompilationError(ByVal sender As Object, ByVal e As CompilationErrorEventArgs)

        Public Sub New(ByVal n As String, Optional ByVal s As String = "")
            If n.ToLower().EndsWith(".rb") Then
                Me.ScriptType = "Ruby"
#If IRONRUBY_AVAILABLE Then
                If ScriptTask.Engine Is Nothing Then
                    ScriptTask.Engine = Ruby.CreateEngine(
                        Sub(lopt)
                            lopt.Options("DefaultEncoding") = RubyEncoding.UTF8
                            lopt.Options("EnableTracing ") = True
                            lopt.Options("DebugVariable") = True
                            Dim lst As New ReadOnlyCollectionBuilder(Of String)
                            lst.Add("ExceptionDetail")
                            lst.Add("ShowClrExceptions")
                            'lst.Add("PassExceptions")
                            lopt.Options("Arguments") = lst.ToReadOnlyCollection()
                        End Sub)
                End If
#If NETFX_CORE Then
                Dim t = GetType(BigDecimal)
                Me.LoadAssembly(t.GetTypeInfo().Assembly)
#Else
                Me.LoadAssembly(GetType(BigDecimal).Assembly)
#End If
#End If
            ElseIf n.ToLower().EndsWith(".py") Then
                Me.ScriptType = "Python"
#If IRONPYTHON_AVAILABLE Then
                If ScriptTask.Engine Is Nothing Then
                    ScriptTask.Engine = Python.CreateEngine()
                End If
#End If
            Else
                Throw New Exception("Unsupported script type.")
            End If
            Me.Name = n
            Me.Script = s
        End Sub

        Public Sub LoadAssembly(ByVal assembly As System.Reflection.Assembly)
            Engine.Runtime.LoadAssembly(assembly)
        End Sub

        Public Sub LoadScriptFromGameDirectory(ByVal path As String, ByVal fileStorage As GameDirectory)
            Try
                Using fs = fileStorage.OpenFile(path, Runa.Foundation.IO.FileMode.Open)
                    Using sr As New StreamReader(fs)
                        Me.Script = sr.ReadToEnd()
                        sr.Close()
                    End Using
                    fs.Close()
                End Using
                Call Me.AddBasicReference(Me)
            Catch ex As Exception
                Throw ex
            End Try
        End Sub

        Public Function Execute(ByVal ParamArray args() As System.MutableTuple(Of String, Object)) As Boolean
            Try
                args = MergeArgs(args)
                If Me.Script = "" Then
                    Return False
                Else
                    While True
                        If Me.ScriptScope IsNot Nothing Then
                            If Me.ScriptScope.GetCode() = Me.Script Then
                                Exit While
                            End If
                        End If
                        Me.ScriptScope = ScriptTask.Engine.CreateScriptSourceFromString(Me.Script)
                        Me.CompiledCode = Me.ScriptScope.Compile(New CompilationErrorListener(Me, AddressOf OnCompilationFailed))
                        Exit While
                    End While
                End If
                For Each var In args
                    Me.CompiledCode.Engine.Runtime.Globals.SetVariable(var.Item1, var.Item2)
                Next
                Me.Result = Me.CompiledCode.Execute()
                Return True
            Catch ex As Exception
                Me.LastError = ex
                RaiseEvent OnRuntimeError(Me, New RuntimeErrorEventArgs(ex))
                Return False
            End Try
        End Function

        Public Function Execute(ByVal expression As String, ByVal ParamArray args() As System.MutableTuple(Of String, Object)) As Boolean
            Try
                args = MergeArgs(args)
                If String.IsNullOrEmpty(Me.m_script) Then
                    Throw New ArgumentException("Null or empty script to execute.")
                    Return False
                Else
                    If String.IsNullOrEmpty(expression) = False Then
                        Dim code = Me.m_script & vbCrLf & expression
                        While True
                            If Me.m_compiledCodes.ContainsKey(code) Then
                                If Me.m_compiledCodes(code).Item1.GetCode() = code Then
                                    Me.CompiledCode = Me.m_compiledCodes(code).Item2
                                    Exit While
                                End If
                            End If
                            Me.ScriptScope = ScriptTask.Engine.CreateScriptSourceFromString(code)
                            Me.CompiledCode = Me.ScriptScope.Compile(New CompilationErrorListener(Me, AddressOf OnCompilationFailed))
                            Me.m_compiledCodes.Add(code, New Tuple(Of ScriptSource, CompiledCode)(Me.ScriptScope, Me.CompiledCode))
                            Exit While
                        End While
                    Else
                        Throw New ArgumentException("Null or empty expression to execute.")
                        Return False
                    End If
                End If
                For Each var In args
                    Me.CompiledCode.Engine.Runtime.Globals.SetVariable(var.Item1, var.Item2)
                Next
                Me.Result = Me.CompiledCode.Execute()
                Return True
            Catch ex As Exception
                Me.LastError = ex
                RaiseEvent OnRuntimeError(Me, New RuntimeErrorEventArgs(ex))
                Return False
            End Try
        End Function

        Private Sub OnCompilationFailed(ByVal sender As Object, ByVal e As CompilationErrorEventArgs)
            RaiseEvent OnCompilationError(sender, e)
        End Sub

        Private Function MergeArgs(ByVal args() As System.MutableTuple(Of String, Object)) As System.MutableTuple(Of String, Object)()
            For Each p In args
                If Me.Parameters.ContainsKey(p.Item1) Then
                    Me.Parameters(p.Item1) = p.Item2
                Else
                    Me.Parameters.Add(p.Item1, p.Item2)
                End If
            Next
            Dim lst As New List(Of System.MutableTuple(Of String, Object))
            For Each p In Me.Parameters
                lst.Add(New System.MutableTuple(Of String, Object)(p.Key, p.Value))
            Next
            Return lst.ToArray()
        End Function

        Private Sub AddBasicReference(ByVal spt As ScriptTask)
            With spt
                .LoadAssembly(Me.GetType().Assembly)
                .LoadAssembly(GetType(SpriteFont).Assembly)
                .LoadAssembly(GetType(SpriteBatch).Assembly)
                .LoadAssembly(GetType(GraphicsDevice).Assembly)
                .LoadAssembly(GetType(Vector2).Assembly)
                .LoadAssembly(GetType(Texture2D).Assembly)
                .LoadAssembly(GetType(GameTime).Assembly)

                .LoadAssembly(GetType(GamePad).Assembly)

                .LoadAssembly(GetType(FontObject).Assembly)
                .LoadAssembly(GetType(Texture2DObject).Assembly)
                .LoadAssembly(GetType(AudioObject).Assembly)
            End With
        End Sub

    End Class

End Namespace